/*
 * Copyright (c) 2012, marco.tamburelli@gmail.com
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met: 
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.bmi.gwt.ws.server.serialization;

import java.util.HashMap;
import java.util.Map;

import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.impl.AbstractSerializationStream;
import com.google.gwt.user.server.rpc.SerializationPolicy;
import com.google.gwt.user.server.rpc.impl.LegacySerializationPolicy;
import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader;
import com.google.gwt.user.server.rpc.impl.TypeNameObfuscator;

/**
 * Provides a collection of functions required to perform serialization and
 * deserialization of objects.
 * 
 * @author Marco Tamburelli
 */
public final class SerializationTool
{
	/**
	 * Maps primitive wrapper classes to their corresponding primitive class.
	 */
	private static final Map<Class<?>, Class<?>> PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS = new HashMap<Class<?>, Class<?>>();

	private static final HashMap<String, Class<?>> TYPE_NAMES;

	static
	{
		PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Boolean.class, Boolean.TYPE);
		PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Byte.class, Byte.TYPE);
		PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Character.class, Character.TYPE);
		PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Double.class, Double.TYPE);
		PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Float.class, Float.TYPE);
		PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Integer.class, Integer.TYPE);
		PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Long.class, Long.TYPE);
		PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Short.class, Short.TYPE);

		TYPE_NAMES = new HashMap<String, Class<?>>();
		TYPE_NAMES.put("Z", boolean.class);
		TYPE_NAMES.put("B", byte.class);
		TYPE_NAMES.put("C", char.class);
		TYPE_NAMES.put("D", double.class);
		TYPE_NAMES.put("F", float.class);
		TYPE_NAMES.put("I", int.class);
		TYPE_NAMES.put("J", long.class);
		TYPE_NAMES.put("S", short.class);
	}

	/**
	 * Returns a default serialization policy.
	 * 
	 * @return the default serialization policy.
	 */
	public static SerializationPolicy getDefaultSerializationPolicy()
	{
		return LegacySerializationPolicy.getInstance();
	}

	/**
	 * Given a type identifier in the stream, attempt to deobfuscate it. Retuns
	 * the original identifier if deobfuscation is unnecessary or no mapping is
	 * known.
	 */
	static String maybeDeobfuscate(ServerSerializationStreamReader streamReader, String name)
			throws SerializationException
	{
		int index;
		if (streamReader.hasFlags(AbstractSerializationStream.FLAG_ELIDE_TYPE_NAMES))
		{
			SerializationPolicy serializationPolicy = streamReader.getSerializationPolicy();
			if (!(serializationPolicy instanceof TypeNameObfuscator)) { throw new IncompatibleRemoteServiceException(
					"RPC request was encoded with obfuscated type names, "
							+ "but the SerializationPolicy in use does not implement "
							+ TypeNameObfuscator.class.getName()); }

			String maybe = ((TypeNameObfuscator) serializationPolicy).getClassNameForTypeId(name);
			if (maybe != null) { return maybe; }
		}
		else if ((index = name.indexOf('/')) != -1) { return name.substring(0, index); }
		return name;
	}

	/**
	 * Returns the {@link Class} instance for the named class or primitive type.
	 * 
	 * @param serializedName the serialized name of a class or primitive type
	 * @param classLoader the classLoader used to load {@link Class}es
	 * @return Class instance for the given type name
	 * @throws ClassNotFoundException if the named type was not found
	 */
	static Class<?> getClassFromSerializedName(String serializedName, ClassLoader classLoader)
			throws ClassNotFoundException
	{
		Class<?> value = TYPE_NAMES.get(serializedName);
		if (value != null) { return value; }

		return Class.forName(serializedName, false, classLoader);
	}

	/**
	 * Returns the {@link java.lang.Class Class} for a primitive type given its
	 * corresponding wrapper {@link java.lang.Class Class}.
	 * 
	 * @param wrapperClass primitive wrapper class
	 * @return primitive class
	 */
	static Class<?> getPrimitiveClassFromWrapper(Class<?> wrapperClass)
	{
		return PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.get(wrapperClass);
	}
}
